﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Drawing;

namespace DTW_Chart
{
    class Calculator
    {
        /// <summary>
        /// Get Euclidean Distance from 2 points
        /// </summary>
        /// <param name="a">First Point</param>
        /// <param name="b">Second Point</param>
        /// <returns></returns>
        public double getEuclideanDistance(PointD a, PointD b)
        {
            return Math.Round(Math.Sqrt(Math.Pow(a.X - b.X, 2) + Math.Pow(a.Y - b.Y, 2)), 3);
        }
        /// <summary>
        /// Translate Sequence a to b
        /// </summary>
        /// <param name="a"></param>
        /// <param name="b"></param>
        public void translateSequence(Sequence a, Sequence b)
        {
            List<PointD> firstSeq = a.getSequence();
            List<PointD> secondSeq = b.getSequence();

            double distanceX = firstSeq[0].X - secondSeq[0].X;
            double distanceY = firstSeq[0].Y - secondSeq[0].Y;

            for (int i = 0; i < firstSeq.Count; i++)
            {
                PointD p = new PointD(firstSeq[i].X - distanceX, firstSeq[i].Y - distanceY);
                firstSeq[i] = p;
            }
        }
        /// <summary>
        /// Get the minimum number of three of them
        /// </summary>
        /// <param name="a"></param>
        /// <param name="b"></param>
        /// <param name="c"></param>
        /// <returns>Return minimum</returns>
        public double findMin(double a, double b, double c)
        {
            if (a < b && a < c)
            {
                return a;
            }
            else if (b < a && b < c)
            {
                return b;
            }
            else
            {
                return c;
            }
        }
        /// <summary>
        /// Make the cost matrix for classic algorithm
        /// </summary>
        /// <param name="firstSeq"></param>
        /// <param name="secondSeq"></param>
        /// <returns>Cost matrix</returns>
        public double[,] makeCostMatrixClassic(List<PointD> firstSeq, List<PointD> secondSeq)
        {
            double[,] costMatrix = new double[firstSeq.Count, secondSeq.Count];
            costMatrix[0, 0] = Math.Round(this.getEuclideanDistance(firstSeq[0], secondSeq[0]), 3);
            //  For D(n,1)
            for (int i = 0; i < firstSeq.Count; i++)
            {
                for (int j = 0; j <= i; j++)
                {
                    costMatrix[i, 0] += Math.Round(this.getEuclideanDistance(firstSeq[j], secondSeq[0]), 3);
                }
            }
            //  For D(1,m)
            for (int i = 0; i < secondSeq.Count; i++)
            {
                for (int j = 0; j <= i; j++)
                {
                    costMatrix[0, i] += Math.Round(this.getEuclideanDistance(firstSeq[0], secondSeq[j]), 3);
                }
            }
            //  For D(n,m)
            for (int i = 1; i < firstSeq.Count; i++)
            {
                for (int j = 1; j < secondSeq.Count; j++)
                {
                    double min = this.findMin(costMatrix[i - 1, j - 1], costMatrix[i - 1, j], costMatrix[i, j - 1]);
                    costMatrix[i, j] = min + Math.Round(this.getEuclideanDistance(firstSeq[i], secondSeq[j]), 3);
                }
            }
            return costMatrix;
        }
        /// <summary>
        /// Make the cost matrix for algorithm with Step Size Condition
        /// </summary>
        /// <param name="firstSeq"></param>
        /// <param name="secondSeq"></param>
        /// <returns>Cost matrix</returns>
        public double[,] makeCostMatrixStepSize(List<PointD> firstSeq, List<PointD> secondSeq)
        {
            double[,] costMatrix = new double[firstSeq.Count, secondSeq.Count];
            costMatrix[0, 0] = Math.Round(this.getEuclideanDistance(firstSeq[0], secondSeq[0]), 3);
            //  For D(n,1)
            for (int i = 0; i < firstSeq.Count; i++)
            {
                for (int j = 0; j <= i; j++)
                {
                    costMatrix[i, 0] += Math.Round(this.getEuclideanDistance(firstSeq[j], secondSeq[0]), 3);
                }
            }
            //  For D(1,m)
            for (int i = 0; i < secondSeq.Count; i++)
            {
                for (int j = 0; j <= i; j++)
                {
                    costMatrix[0, i] += Math.Round(this.getEuclideanDistance(firstSeq[0], secondSeq[j]), 3);
                }
            }

            for (int i = 1; i < firstSeq.Count; i++)
            {
                
                for (int j = 1; j < secondSeq.Count; j++)
                {
                    double min1 = costMatrix[i - 1, j - 1]
                                + Math.Round(this.getEuclideanDistance(firstSeq[i], secondSeq[j]), 3);
                    double min2 = Int32.MaxValue;
                    if (i > 1)
                    {
                        min2 = costMatrix[i - 2, j - 1]
                             + Math.Round(this.getEuclideanDistance(firstSeq[i - 1], secondSeq[j]), 3)
                             + Math.Round(this.getEuclideanDistance(firstSeq[i], secondSeq[j]), 3);
                    }
                    double min3 = Int32.MaxValue;
                    if (j > 1)
                    {
                        min3 = costMatrix[i - 1, j - 2]
                             + Math.Round(this.getEuclideanDistance(firstSeq[i], secondSeq[j - 1]), 3)
                             + Math.Round(this.getEuclideanDistance(firstSeq[i], secondSeq[j]), 3);
                    }
                    double min4 = Int32.MaxValue;
                    if (i > 2)
                    {
                        min4 = costMatrix[i - 3, j - 1]
                             + Math.Round(this.getEuclideanDistance(firstSeq[i - 2], secondSeq[j]), 3)
                             + Math.Round(this.getEuclideanDistance(firstSeq[i - 1], secondSeq[j]), 3)
                             + Math.Round(this.getEuclideanDistance(firstSeq[i], secondSeq[j]), 3);
                    }
                    double min5 = Int32.MaxValue;
                    if (j > 2)
                    {
                        min5 = costMatrix[i - 1, j - 3]
                             + Math.Round(this.getEuclideanDistance(firstSeq[i], secondSeq[j - 2]), 3)
                             + Math.Round(this.getEuclideanDistance(firstSeq[i], secondSeq[j - 1]), 3)
                             + Math.Round(this.getEuclideanDistance(firstSeq[i], secondSeq[j]), 3);
                    }
                    double min_temp = this.findMin(min1, min2, min3);
                    double min = this.findMin(min_temp, min4, min5);
                    costMatrix[i, j] = min;
                }
            }

            return costMatrix;
        }
        public double[,] makeCostMatrixLocalWeight(List<PointD> firstSeq, List<PointD> secondSeq)
        {
            double[,] costMatrix = new double[firstSeq.Count, secondSeq.Count];
            costMatrix[0, 0] = Math.Round(this.getEuclideanDistance(firstSeq[0], secondSeq[0]), 3);
            //  For D(n,1)
            for (int i = 0; i < firstSeq.Count; i++)
            {
                for (int j = 0; j <= i; j++)
                {
                    costMatrix[i, 0] += Math.Round(this.getEuclideanDistance(firstSeq[j], secondSeq[0]), 3);
                }
            }
            //  For D(1,m)
            for (int i = 0; i < secondSeq.Count; i++)
            {
                for (int j = 0; j <= i; j++)
                {
                    costMatrix[0, i] += Math.Round(this.getEuclideanDistance(firstSeq[0], secondSeq[j]), 3);
                }
            }

            //  Local Weight
            for (int i = 1; i < firstSeq.Count; i++)
            {
                for (int j = 1; j < secondSeq.Count; j++)
                {
                    double min1 = costMatrix[i - 1, j - 1] 
                                + 2 * Math.Round(this.getEuclideanDistance(firstSeq[i], secondSeq[j]), 3);
                    double min2 = costMatrix[i - 1, j]
                                + Math.Round(this.getEuclideanDistance(firstSeq[i], secondSeq[j]), 3);
                    double min3 = costMatrix[i, j - 1]
                                + Math.Round(this.getEuclideanDistance(firstSeq[i], secondSeq[j]), 3);

                    double min = this.findMin(min1, min2, min3);
                    costMatrix[i, j] = min;
                }
            }

            return costMatrix;
        }
        /// <summary>
        /// Print out Cost Matrix to illustrate
        /// </summary>
        /// <param name="costMatrix"></param>
        public void printOutCostMatrix(double[,] costMatrix)
        {
            string specifier = "00.000";
            for (int i = 0; i < costMatrix.GetLength(0); i++)
            {
                for (int j = 0; j < costMatrix.GetLength(1); j++)
                {
                    Console.Write("{1}", specifier, costMatrix[i, j].ToString(specifier) + "  ");
                }
                Console.WriteLine();
                Console.WriteLine();
            }
        }
    }
}
